package logic.gridSimInterface.user;

//~--- non-JDK imports --------------------------------------------------------

import eduni.simjava.Sim_event;
import eduni.simjava.distributions.Sim_normal_obj;
import eduni.simjava.distributions.Sim_uniform_obj;

import gridsim.*;

import gridsim.net.SimpleLink;

import logic.JSimulation;
import logic.Statistic;
import logic.distributions.AbstractGenerator;
import logic.distributions.ConstantGenerator;
import logic.distributions.UniformGenerator;

import logic.gridSimInterface.SimTags;

//~--- JDK imports ------------------------------------------------------------

/*
Author Miroslav Bellak
Date: May 2007
 */
import java.util.*;

public class UserSimple extends GridSim {
    //STATISTICS
    private Accumulator gridletWaitTime = new Accumulator();
    private Accumulator gridletSubmitReturnTime = new Accumulator();

    private Accumulator gridletExecutionTime = new Accumulator();
    private HashMap<Integer, Double> gridletInfoReturn = null;
    
    
    private String                    brokerName           = null;
    private GridletList               finishedGridletList  = null;    // processed gridlets recived from resource
    private GridletList               gridletList          = null;    // generated gridlets that will be send to resource for processing
    private GridletList               unfittingGridletList = null;    // gridlets that can not be processed because no resource can support wanted architecture or operating system
    private boolean                   online               = true;

    private HashMap<Integer, Integer>   gridletBroker        = null;

    private int     gridletCount         = 0;
    private long    seed                 = 1;
    private int     planning             = SimTags.DYNAMIC_PLANNING;

    public UserSimple(String name, SimpleLink link, String brokerName, UserGenerationParameters params)
            throws Exception {
        super(name, link);
        this.finishedGridletList  = new GridletList();
        this.unfittingGridletList = new GridletList();

        this.brokerName   = brokerName;
        this.gridletCount = params.tasksCount;

        this.gridletList  = generateGridlets(params);

        if (params.gridletType == SimTags.GRIDLET_TYPE_WORKFLOW) {
            generateWorkFlow(params.workflow);
        }

        this.gridletBroker        = new HashMap<Integer, Integer>(gridletCount);
        this.gridletInfoReturn    = new HashMap<Integer, Double>(gridletCount);
    }

    
    public void body() {
        Sim_event event = new Sim_event();

        // true, ak uz poslal Hermesovi spravu o tom, ze dostal spat vsetky ulohy
        boolean jobsFinishedReported = false;

        // Gets one event at a time
        for (super.sim_get_next(event); event.get_tag() != GridSimTags.END_OF_SIMULATION; super.sim_get_next(event)) {
            GridletComplex gridlet     = null;
            GridletInfo    gridletInfo = null;

            switch (event.get_tag()) {
            case SimTags.SIMULATION_START :
                System.out.println("Entity " + this.get_name() + " recieved start simulation at time "
                                   + GridSim.clock());
                this.planning = ((Integer) event.get_data()).intValue();

                if (this.planning == SimTags.DYNAMIC_PLANNING) {
                    for (Iterator iter = gridletList.iterator(); iter.hasNext(); ) {
                        GridletComplex element = (GridletComplex) iter.next();

                        super.send(this.get_name(), element.getCreationTime(), SimTags.GRIDLET_READY, element);
                    }
                } else {
                    super.send(this.get_name(), GridSimTags.SCHEDULE_NOW, SimTags.GRIDLET_READY);
                }

                break;

            case SimTags.GRIDLET_READY :
                if (planning == SimTags.STATIC_PLANNING) {
                    LinkedList<GridletInfo> gridletInfosList = new LinkedList<GridletInfo>();

                    for (Iterator iter = gridletList.iterator(); iter.hasNext(); ) {
                        gridlet     = (GridletComplex) iter.next();
                        gridletInfo = new GridletInfo(gridlet.getUserID(), gridlet);
                        gridletInfosList.add(gridletInfo);
                    }

                    super.send(this.brokerName, GridSimTags.SCHEDULE_NOW, SimTags.GRIDLETINFO_SUBMIT, gridletInfosList);
                    System.out.println("Entity " + super.get_name() + " send gridletInfo list at time "
                                       + +GridSim.clock());
                } else if (planning == SimTags.DYNAMIC_PLANNING) {
                    gridlet = (GridletComplex) event.get_data();

                    if ((gridlet.getCreationTime() <= GridSim.clock()) && this.online
                            && isPredecessorFinished(gridlet)) {
                        gridletInfo = new GridletInfo(gridlet.getUserID(), gridlet);
                        super.send(this.brokerName, GridSimTags.SCHEDULE_NOW, SimTags.GRIDLETINFO_SUBMIT, gridletInfo);
                        System.out.println("Entity " + super.get_name() + " will send gridletInfo ID "
                                           + gridlet.getGridletID() + " at time " + GridSim.clock());
                    }
                }

                break;

            case SimTags.GRIDLETINFO_RETURN :
                gridletInfo = (GridletInfo) event.get_data();
                gridletBroker.put(gridletInfo.getGridletId(), gridletInfo.getBrokerId());
                gridlet = getGridlet(gridletInfo.getGridletId());

                gridletInfoReturn.put(gridletInfo.getGridletId(), GridSim.clock());
                gridletWaitTime.add( GridSim.clock() - gridletInfo.getCreationTime());

                if (gridlet != null) {
                    super.send(gridletInfo.getResourceId(), GridSimTags.SCHEDULE_NOW, GridSimTags.GRIDLET_SUBMIT,
                               gridlet);
                    System.out.println("Entity " + super.get_name() + " send gridlet ID " + gridlet.getGridletID()
                                       + " to " + super.getEntityName(gridletInfo.getResourceId()) + " at time "
                                       + GridSim.clock());
                } else {
                    System.out.println("Error gridlet #" + gridletInfo.getGridletId() + " not found in "
                                       + this.getName() + " gridlet list");
                }

                break;

            case GridSimTags.GRIDLET_RETURN :
                JSimulation.getState().incDoneTasksCount();
                gridlet     = (GridletComplex) event.get_data();
                gridletInfo = new GridletInfo(gridlet.getUserID(), gridlet);
                gridletInfo.setResourceId(gridlet.getResourceID());
                finishedGridletList.add((Object)gridlet);
                
                gridletExecutionTime.add(GridSim.clock() - gridletInfoReturn.get(gridletInfo.getGridletId()));
                gridletSubmitReturnTime.add(GridSim.clock() - gridletInfo.getCreationTime());

                if (this.planning == SimTags.DYNAMIC_PLANNING) {
                    for (Iterator iter = gridletList.iterator(); iter.hasNext(); ) {
                        GridletComplex gridComplex = (GridletComplex) iter.next();

                        if ((gridlet.getGridletID() == gridComplex.getPredecessorId())) {
                            GridletInfo grI = new GridletInfo(gridComplex.getUserID(), gridComplex);

                            super.send(this.brokerName, gridComplex.getCreationTime() - GridSim.clock(),
                                       SimTags.GRIDLETINFO_SUBMIT, grI);
                            System.out.println("Entity " + super.get_name() + " will send gridletInfo ID "
                                               + gridComplex.getGridletID() + " at time "
                                               + gridComplex.getCreationTime());
                        }
                    }
                }

                int broker = gridletBroker.get(gridletInfo.getGridletId());

                super.send(broker, GridSimTags.SCHEDULE_NOW, SimTags.GRIDLETINFO_FINISHED, gridletInfo);
                System.out.println("Entity " + super.get_name() + " recived back gridlet ID " + gridlet.getGridletID()
                                   + " at time " + GridSim.clock() + " from "
                                   + super.getEntityName(gridlet.getResourceID()));

                break;

            case SimTags.UNABLE_TO_PROCESS_GRIDLET :
                JSimulation.getState().incDoneTasksCount();
                gridletInfo = (GridletInfo) event.get_data();
                gridlet     = getGridlet(gridletInfo.getGridletId());
                unfittingGridletList.add(gridlet);
                System.out.println("Unfitting gridlet added to list at time " + GridSim.clock());

                break;

            case SimTags.USER_OFFLINE :
                this.online = false;
                super.send(this.brokerName, GridSimTags.SCHEDULE_NOW, SimTags.USER_OFFLINE, new Integer(this.get_id()));

                break;

            case SimTags.USER_ONLINE :
                this.online = true;

                LinkedList<GridletInfo> gridletInfosList = new LinkedList<GridletInfo>();

                if (planning == SimTags.STATIC_PLANNING) {
                    for (Iterator iter = gridletList.iterator(); iter.hasNext(); ) {
                        gridlet     = (GridletComplex) iter.next();
                        gridletInfo = new GridletInfo(gridlet.getUserID(), gridlet);
                        gridletInfosList.add(gridletInfo);
                    }
                } else if (planning == SimTags.DYNAMIC_PLANNING) {
                    for (Iterator iter = gridletList.iterator(); iter.hasNext(); ) {
                        gridlet = (GridletComplex) iter.next();

                        if ((gridlet.getCreationTime() <= GridSim.clock()) && this.online) {
                            gridletInfo = new GridletInfo(gridlet.getUserID(), gridlet);
                            gridletInfosList.add(gridletInfo);
                        }
                    }
                }

                super.send(this.brokerName, GridSimTags.SCHEDULE_NOW, SimTags.USER_ONLINE, gridletInfosList);
                System.out.println("Entity " + super.get_name() + " send gridletInfo list at time " + +GridSim.clock());

                break;
            }

            // all generated gridlets has been finished and sent back to user
            if (this.gridletCount == (this.finishedGridletList.size() + this.unfittingGridletList.size())) {
                if (!jobsFinishedReported) {
                    super.send(JSimulation.getHermesName(), GridSimTags.SCHEDULE_NOW, SimTags.NO_MORE_GRIDLETS);
                    jobsFinishedReported = true;
                }
            }
        }

        double waitTime = 0;
        if (gridletWaitTime.getCount() > 0) {
            waitTime = gridletWaitTime.getMean();
        }
        JSimulation.addStatistic(new Statistic(
                Statistic.GRIDLET_WAIT_TIME,
                get_name(),
                waitTime
                ));
        
        double submitReturnTime = 0;
        if (gridletSubmitReturnTime.getCount() > 0) {
            submitReturnTime = gridletSubmitReturnTime.getMean();
        }
        JSimulation.addStatistic(new Statistic(
                Statistic.GRIDLET_SUBMIT_RETURN_TIME,
                get_name(),
                submitReturnTime
                ));
        
        double executionTime = 0;
        if (gridletExecutionTime.getCount() > 0) {
            executionTime = gridletExecutionTime.getMean();
        }
        JSimulation.addStatistic(new Statistic(
                Statistic.GRIDLET_EXECUTION_TIME,
                get_name(),
                executionTime
                ));
        
        JSimulation.addStatistic(new Statistic(
                Statistic.FINISHED_GRIDLETS_COUNT,
                get_name(),
                finishedGridletList.size()
                ));
        
        JSimulation.addStatistic(new Statistic(
                Statistic.UNFITTED_GRIDLETS_COUNT,
                get_name(),
                unfittingGridletList.size()
                ));
        
        // when simulation ends, terminate the Input and Output entities
        super.sim_get_next(event);

        if (event.get_tag() == GridSimTags.END_OF_SIMULATION) {
            super.terminateIOEntities();
        }
    }

    /**
     * This method will genrate gridlets according to given paramters
     * @param gridletCount number of gridlet thta will be generated
     * @param gridletType tye of generated gridlets
     * @param gridletDistribution whot kind of distribution will be used to genereta gridlet start time
     * @return a GridletList list of generated gridlets
     */
    private GridletList generateGridlets( UserGenerationParameters par) {
        
        GridletList retVal      = new GridletList();
        
        double      length      = 0.0;
        long        file_size   = 0;
        long        output_size = 0;
        boolean     record      = false;
        double      startTime   = 0.0;

        GridSimStandardPE.setRating(100);

        par.fileSize.initGenerator("FileSizeGenerator_" + get_name());
        par.outputSize.initGenerator("OutputSizeGenerator_"+get_name());
        par.taskLength.initGenerator("TaskLengthGenerator_" + get_name());

        if (par.gridletType != SimTags.GRIDLET_TYPE_SIMPLE)
            par.startTime.initGenerator("StartTimeGenerator_" + get_name());

        for (int i = 0; i < par.tasksCount; i++) {
            length      = par.taskLength.getDiscreteValue();
            file_size   = par.fileSize.getDiscreteValue();
            output_size = par.outputSize.getDiscreteValue();

            startTime = 0;
            if (par.gridletType != SimTags.GRIDLET_TYPE_SIMPLE) {
                startTime = startTime + par.startTime.getContinuousValue();
            }

            GridletComplex gridlet = new GridletComplex(i, length, file_size, output_size, record, startTime,
                                         par.os, par.architecture);

            gridlet.setUserID(this.get_id());

            if (par.gridletType != SimTags.GRIDLET_TYPE_SIMPLE) {
                gridlet.generateRandomCompletionTerm(this.seed);
            }

            retVal.add(gridlet);
        }

        return retVal;
    }

    /**
     * This function will add predecessor IDs to count of gridlets in gridletList
     * @param count of predecessor
     */
    private void generateWorkFlow(int count) {
        Random         random             = new Random(this.seed + 111);
        GridletComplex gridletWithMinTime = (GridletComplex) gridletList.getFirst();

        for (Iterator iter = gridletList.iterator(); iter.hasNext(); ) {
            GridletComplex element = (GridletComplex) iter.next();

            if (element.getCreationTime() < gridletWithMinTime.getCreationTime()) {
                gridletWithMinTime = element;
            }
        }

        while (count > 0) {
            GridletComplex gridlet = (GridletComplex) gridletList.get(random.nextInt(gridletList.size()));

            while ((gridlet.getPredecessorId() != -1)
                    || (gridlet.getCreationTime() <= gridletWithMinTime.getCreationTime())) {
                gridlet = (GridletComplex) gridletList.get(random.nextInt(gridletList.size()));
            }

            ArrayList      elements    = new ArrayList();
            GridletComplex predecessor = (GridletComplex) gridletList.get(random.nextInt(gridletList.size()));

            while (gridlet.equals(predecessor) || (predecessor.getCreationTime() >= gridlet.getCreationTime())
                    || hasCycle(elements, predecessor) ||!isCorrectWorkFlow(gridlet, predecessor)) {
                predecessor = (GridletComplex) gridletList.get(random.nextInt(gridletList.size()));
            }

            gridlet.setPredecessorId(predecessor.getGridletID());
            count--;
        }
    }

    /**
     * Finds out if all gridlets in
     * @param gridlet new gridlet that we are adding
     * @param predecessor new predecessor of gridlet
     * @return true if chain of gridlet is correct, flase otherwise
     */
    private boolean isCorrectWorkFlow(GridletComplex gridlet, GridletComplex predecessor) {
        if (predecessor.getCreationTime() >= gridlet.getCreationTime()) {
            return false;
        }

        if (predecessor.getPredecessorId() == -1) {
            return true;
        } else {
            return isCorrectWorkFlow(predecessor, giveGridlet(predecessor.getPredecessorId()));
        }
    }

    /**
     * Find out if new chain of gridlets has cycles or not
     * @param elements gridlet chain
     * @param gridlet that we are trying to put in grildet chain without making cycles
     * @return true if gridlet and elemets form cycle, false otherwise
     */
    private boolean hasCycle(ArrayList elements, GridletComplex gridlet) {
        if (gridlet.getPredecessorId() == -1) {
            return false;
        }

        boolean found = false;

        for (Iterator iter = elements.iterator(); iter.hasNext(); ) {
            Integer id = (Integer) iter.next();

            if (id.intValue() == gridlet.getPredecessorId()) {
                found = true;

                break;
            }
        }

        if (found) {
            return true;
        } else {
            elements.add(new Integer(gridlet.getGridletID()));

            return hasCycle(elements, giveGridlet(gridlet.getPredecessorId()));
        }
    }

    /**
     * Finds out if given gridlet has predecessor and if this predecessor is completed
     * @param gridlet whose predecessor we are looking for
     * @return true if gridlet has no predecessor or his rpedecessor is already completed, false otherwise
     */
    private boolean isPredecessorFinished(GridletComplex gridlet) {
        if (gridlet.getPredecessorId() == -1) {
            return true;
        }

        for (Iterator iter = finishedGridletList.iterator(); iter.hasNext(); ) {
            GridletComplex element = (GridletComplex) iter.next();

            if (element.getGridletID() == gridlet.getPredecessorId()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Removes gridlet with given ID from gridletList and returns it
     * @param gridletId ID of wanted gridlet
     * @return wanted Gridlet or null if not found
     */
    private GridletComplex getGridlet(int gridletId) {
        GridletComplex gridlet = null;

        for (int i = 0; i < gridletList.size(); i++) {
            GridletComplex element = (GridletComplex) gridletList.get(i);

            if (element.getGridletID() == gridletId) {
                gridlet = element;
                gridletList.remove(i);

                break;
            }
        }

        return gridlet;
    }

    /**
     * Finds gridlet with given ID in gridletList and returns it
     * @param gridletId ID of wanted gridlet
     * @return wanted Gridlet or null if not found
     */
    private GridletComplex giveGridlet(int gridletId) {
        GridletComplex gridlet = null;

        for (int i = 0; i < gridletList.size(); i++) {
            GridletComplex element = (GridletComplex) gridletList.get(i);

            if (element.getGridletID() == gridletId) {
                gridlet = element;

                break;
            }
        }

        return gridlet;
    }

}
